CAS,Compare And Swap,即比较并交换,整个AQS同步组件、Atomic原子类操作等等都是以CAS实现的,甚至ConcurrentHashMap在1.8的版本中也调整为了CAS+Synchronized,可以说CAS是整个JUC的基石。
对于并发控制而言,锁是一种悲观策略,会阻塞线程执行,而无锁是一种乐观策略,它会假设对资源的访问是没有冲突的,既然没有冲突就不需要等待,线程不需要阻塞。
CAS分析
在CAS中有三个参数,内存位置V、旧的预期值A、要更新的值B,当且仅当内存位置V的值等于旧的预期值A时才会将内存位置V的值修改为B,否则什么也不干,其伪代码如下:
|
|
其实上面这段伪代码也不是线程安全的,如果两个线程分别都通过了this.value == A
的判断,那么最终value的值就未知了。
JUC下的atomic类都是通过CAS来实现的,下面就以AtomicInteger为例来阐述CAS的操作,如下:
|
|
Unsafe是CAS的核心类,Java无法直接访问底层操作系统,而是通过本地方法来访问,但是JVM还是开了一个后门:Unsafe,它提供了硬件级别的原子操作。
valueOffset为变量值在内存中的偏移地址,unsafe就是通过偏移地址来得到数据的原值的。
value当前值,使用volatile修饰,保证多线程中看见的是同一个。
下面看下AtomicInteger中的addAndGet()方法:
|
|
内部调用unsafe的getAndAddInt方法,在getAndAddInt方法中又调用了compareAndSwapInt方法,其具有四个参数,分别表示对象、对象的地址、预期值、修改值。
我们再看下AtomicInteger类中的一个方法:compareAndSet,顾名思义,如果当前值等于预期值,则以原子方式将该值设置为给定的更新值:
|
|
可以发现,compareAndSet底层调用的仍然是compareAndSwapInt方法。
再看一个方法getAndIncrement:
|
|
这里就是先获取value的值,再加1,然后进行CAS操作,如果在读取value值和加1的过程中value的值被其他线程改变了,那么CAS失败,一直循环到成功为止。
CAS的ABA问题
因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有变化就更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了,ABA问题的解决思路就是使用版本号,在变量前面加上版本号,每次变量更新的时候把版本号加1,那么A-B-A就会变成1A-2B-3A,从Java1.5开始的JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题,这个类的compareAndSet方法作用是先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。